home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1997 January: Mac OS SDK / Dev.CD Jan 97 SDK2.toast / Development Kits (Disc 2) / OpenDoc / Developer Documentation / Recipes, Tech Notes & Articles / Tech Notes / Utilities Documentation / Temporary References⁄Objects < prev    next >
Encoding:
Text File  |  1995-11-06  |  9.6 KB  |  161 lines  |  [TEXT/ttxt]

  1. OpenDoc™ Recipes
  2.  
  3.  
  4. Temporary References & Objects
  5. 27 September 1995
  6.  
  7.  
  8. © 1995 Apple Computer, Inc. All Rights Reserved.
  9. Apple, the Apple logo, and Macintosh are registered trademarks of Apple Computer, Inc.
  10. Mac and OpenDoc are trademarks of Apple Computer, Inc. 
  11.  
  12.  
  13. The Problem & the Solution
  14.  
  15. When writing OpenDoc-based code, one often needs to create temporary objects that need to be freed, or acquire temporary references to objects that then need to be released. There are two pitfalls with this — the obvious one is that you have to remember to call 'delete' or 'Release' on every temporary object. The not so obvious one is that, if you are using the Except utility and an exception is thrown while a temporary is active, the rug will be pulled out from under you and you won't be able to issue the 'delete' or 'Release' call. This results in a memory or ref-count leak, unless you put in an exception handler whose job is to clean up these temporaries, which complicates your code and introduces even more room for subtle errors.
  16.  
  17. Here's an example of code that uses a temporary reference:
  18. {
  19.       ODShape *s = frame->GetFrameShape(ev,kODNULL);
  20.       DoSomethingTo(s);
  21.       s->Release(ev);
  22. }
  23.  
  24. We had to remember to call Release on s after we were through with it. And we still have the problem that, if DoSomething throws an exception, s will be left dangling. We could make the code exception-safe by rewriting it as:
  25. {
  26.       ODShape *s = frame->GetFrameShape(ev,kODNULL);
  27.       TRY
  28.             DoSomethingTo(s);
  29.       CATCH_ALL
  30.             s->Release(ev);
  31.             RERAISE;
  32.       ENDTRY
  33.       s->Release(ev);
  34. }
  35.  
  36. This makes the code more complicated (and larger), and you have to repeat the Release call twice. It's easy to get this wrong, resulting in code that works fine unless an exception is thrown, in which case it does the wrong thing. Since exceptions happen rarely in normal use, this results in spurious bugs. Ewww.
  37.  
  38. An elegant solution to the problem is to make use of stack-based C++ objects whose destructors will be called whenever they go out of scope, whether through exiting a block normally, or via an exception. Our exception library implements these, whether or not native C++ exceptions are supported, and calls them Destructos.
  39.  
  40. Using Destructos and some clever operator overloads, it's possible to make little objects that pretend to be pointers to other types of objects, and can be used just as though they were pointers.  Unlike pointers, however, they clean themselves up automatically.
  41.  
  42. Using this facility, we can rewrite our routine as:
  43. {
  44.       TempODShape s = frame->GetFrameShape(ev,kODNULL);
  45.       DoSomethingTo(s);
  46. }
  47.  
  48. All we had to do was change “ODShape*” to “TempODShape”, and take out the Release call. The rest is the same. Although s is now an actual (stack-based) object, not a pointer, it can still be used as though it were an ODShape*. In particular, the following kinds of things are legal and do what you'd expect:
  49.       s->GetBoundingBox( ... )
  50.       xform->TransformShape(ev,s);
  51.       if( s != kODNULL ) ...
  52.       if( s ) ...
  53.       s = frame->GetUsedShape(ev,kODNULL);    // Note that this does not release the shape s used to point to!
  54.       s = kODNULL;
  55.  
  56. The Release happens automatically when the block exits or if DoSomething throws an exception. Of course, if s holds a pointer to kODNULL, no Release operation will occur.
  57.  
  58. Using TempObjs and TempRefs
  59.  
  60. To use this facility, just #include <TempObj.h> in your source files. This gives you access to the following classes:
  61.  
  62. TempODFrame
  63. TempODPart
  64. TempODShape
  65. TempODStorageUnit
  66. TempODTransform
  67. TempODWindow
  68.  
  69. TempODFocusSetIterator
  70. TempODFrameFacetIterator
  71.  
  72. (Note that iterators are not ref-counted, so the Temp___Iterator classes delete the iterator object at the end instead of releasing it.) 
  73.  
  74. If your compiler supports C++ templates (both CodeWarrior and Symantec C++ do) you can #define a symbol _USE_TEMPLATES_ before including TempObj.h. This will ensure that the header uses templates to implement these classes. This might make the implementation more efficient, and it also makes it much easier to extend the mechanism to new classes (see below.) If you can't or don't want to use templates, just don't define this symbol; the default is that the classes are implemented without using templates.
  75.  
  76. NOTE: There is a bug in the DR3 version of TempObj.h that will cause type-mismatch errors (probably at line 139) if you are using _USE_TEMPLATES_, unless you include <FrFaItr.xh> and <FacetItr.xh> before TempObj.h. In other words, include it as follows:
  77. #include <FrFaItr.xh>
  78. #include <FacetItr.xh>
  79. #include <TempObj.h>
  80.  
  81. Pitfalls
  82.  
  83. The biggest mistake you can make in using this utility is forgetting that the object is always released. This can bite you if you need to use the object as the return value of a function:
  84.  
  85. ODShape *foo( ODFrame *frame ) {
  86.     TempODShape s = frame->GetFrameShape(ev,kODNULL);
  87.     DoSomething(s);
  88.     return s;
  89. }
  90.  
  91. The ODShape is going to be released before it's returned, when the destructor of "s" is called. This is bad news, since the function will return either a pointer to a deleted object, or to an object whose ref-count is one too low. Either case is likely to cause a crash.
  92. It's still nice to use a TempODShape in this function, for safety in case DoSomething throws an exception. We just want to tell "s" not to release itself when it's being returned. You can do this by setting the shape to kODNULL before returning it:
  93.  
  94. ODShape *foo( ODFrame *frame ) {
  95.     TempODShape s = frame->GetFrameShape(ev,kODNULL);
  96.     DoSomething(s);
  97.     ODShape *temp = s;
  98.     s = kODNULL;         // s will not be released by the destructor now
  99.     return temp;
  100. }
  101.  
  102. Of course, this is a kludge in that we have to store the value of s in a temporary to keep it from being lost. But there is a convenience method called DontRelease that will set the reference to NULL but return its previous value:
  103.  
  104. ODShape *foo( ODFrame *frame ) {
  105.     TempODShape s = frame->GetFrameShape(ev,kODNULL);
  106.     DoSomething(s);
  107.     return s.DontDelete();      // Note that we used ".", not "->"
  108. }
  109.  
  110. Using Temp Iterators
  111.  
  112. The utility TempIter contains some extra classes that are TempObj's for OpenDoc iterator classes. In addition to managing the automatic deletion of the iterator itself, they also simplify the process of using the iterator (and shrink the resulting code.) For example:
  113.  
  114.             extern void DoSomethingWith( ODFoo* );
  115.             extern void OrSomethingWith( ODFoo* );
  116.             ...
  117.             ODBazz *bazz;
  118.             ...
  119.             for( TempODFooIterator iter(ev,bazz); iter.Current(); iter.Next() ) {
  120.                 DoSomethingWith(iter);
  121.                 OrSomethingWith(iter.Current());
  122.                 // within the loop you can use iter.Current() or just iter to refer to the current object it's pointing to.
  123.             }
  124.  
  125. True C++meisters can even shrink the for loop down to this:
  126.  
  127.             for( TempODFooIterator iter(ev,bazz); iter; iter++ )
  128.  
  129. since the iterator itself can be used as a synonym for its current object, and the "++" operator is the same as calling Next.
  130.  
  131. Adding New Classes
  132.  
  133. There are other classes you might want to have Temp____ objects available for.
  134.  
  135. If you're using templates (by defining _USE_TEMPLATES_ before including TempObj.h, as described above) this is very easy. You can declare a temporary reference to any type of ref-counted object by using the class TempRef<classname>. For instance:
  136.  
  137. TempRef<ODDraft> su = doc->AcquireDraft(ev);
  138.  
  139. You can also use temporary instances of non-ref-counted objects by using TempObj<class>:
  140.  
  141. TempObj<ODPeanutIterator> iter = peanut->GetIterator(ev);
  142.  
  143. If you're not using templates, you'll need to do some more work, adding several weird looking #defines and #includes. You can add these to the existing TempObj files, or in your own files. We'll describe the former case here.
  144.  
  145. Open TempObj.h. Scroll down to the comment that reads “Instantiations of TempObj and TempRef. Add your own if necessary.” Down past the “#else /* not _USE_TEMPLATES_*/” line, you'll see a series of groups of lines, each of which looks like:
  146.  
  147.     #define _T_        ODFrame
  148.     #define _C_        TempODFrame
  149.     #include "TempRef.th"
  150.  
  151. Just add another one of these groups (it could be in TempObj.h, or you could put it into a separate header of your own) changing _T_ to the OpenDoc class and _C_ to the name of the temporary-reference class. If the OpenDoc class is not ref-counted, you'll need to #include "TempObj.th" instead.
  152.  
  153. Then open TempObj.cpp. Scroll down to the comment that reads “// Define the non-inline methods of the various template classes:”. Below that you'll see another list of #defines and #includes like the ones shown above. Again, add another one of these groups just like you did in TempObj.h.
  154.  
  155. You'll need to recompile TempObj.cpp. If this is in a separate utility library and not directly in your project, you'll need to build the library first (in CodeWarrior, open the library's project and choose the Make command.) If TempObj.h is precompiled, the very first thing you'll have to do is rebuild the precompiled header.
  156.  
  157. If, after adding a new class, you get a type-mismatch error in TempObj.h (probably at line 139) or in TempObj.th or TempRef.th, this indicates that you are trying to use TempObj with a class that is not a subclass of ODObject, or TempRef with a class that is not a subclass of ODRefCntObject. This can happen even if the class is correct, if the compiler hasn't seen the declaration of the class before the declaration of the temporary. In other words, the following is wrong:
  158.     class ODFoo;
  159.     TempRef<ODFoo> ref = ......;
  160.     #include "ODFoo.h"
  161. At the time that the compiler instantiates the template for ODFoo, it does not know anything about the class, such as whether it is a subclass of ODRefCntObject, and will therefore give you type-check errors. You can avoid this by including the header for ODFoo before using the TempRef class.